/**
 * Copyright 2012 Roger Cockshutt
 * krakenbrau@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.parallelrl.process;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import com.googlecode.blacken.terminal.BlackenKeys;
import com.googlecode.parallelrl.Config;
import com.googlecode.parallelrl.data.ECSLevel;
import com.googlecode.parallelrl.data.KeyCode;
import com.googlecode.parallelrl.data.LevelProvider;
import com.googlecode.parallelrl.data.ecs.ECSManager;
import com.googlecode.parallelrl.display.Colors;
import com.googlecode.parallelrl.display.GameLogger;
import com.googlecode.parallelrl.display.Menu;
import com.googlecode.parallelrl.display.Screen;
import com.googlecode.parallelrl.util.ComponentPair;
import com.googlecode.parallelrl.util.Names;
import com.googlecode.parallelrl.util.Point2D;

/**
 * @author Roger Cockshutt
 *
 */
public class BehaviourManager {
	
	public static final List<String> primaryAttributes;
	public static final List<String> secondaryAttributes;
	static {
		String[] primaries = {
				"Strength",
				"Dexterity",
				"Agility",
				"Constitution",
				"Intelligence",
				"Wisdom",
				"Creativity",
				"Perception"
		};
		String[] secondaries = {
				"PhysicalAttack",
				"PhysicalAccuracy",
				"PhysicalDefense",
				"PhysicalEvasion",
				"PhysicalSpeed",
				"MentalAttack",
				"MentalAccuracy",
				"MentalDefense",
				"MentalEvasion",
				"MentalSpeed",
				"SpecialDefense",
				"SpecialInflict",
				"MovementSpeed"
		};
		primaryAttributes = Arrays.asList(primaries);
		secondaryAttributes = Arrays.asList(secondaries);
	}
	
	private static Long lastActiveControllableEntity;
	
	public static void act(Long... entities) {
		act(Arrays.asList(entities));
	}
	
	public static void setLastActiveControllable(Long entity) {
		lastActiveControllableEntity = entity;
	}
	
	public static Long getLastActiveControllable() {
		return lastActiveControllableEntity;
	}
	
	@SuppressWarnings("unchecked")
	public static void act(Long entity) {
		ComponentPair behaviour = ECSManager.getComponent(entity, "Behaviour");
		if (behaviour == null) {
			return;
		}
		if (behaviour.value().equals("PlayerControlled")) {
			setLastActiveControllable(entity);
			Game.updateAll();
		}
		
		List<HashMap<String, String>> events = null;
		try {
			Method behaviourMethod;
			behaviourMethod = Behaviours.class.getDeclaredMethod(behaviour.value().toLowerCase(), Long.class);
			events = (List<HashMap<String, String>>) behaviourMethod.invoke(null, entity);
			if (!Game.running) {
				return;
			}
		} catch (NoSuchMethodException | SecurityException | 
				IllegalAccessException | IllegalArgumentException | 
				InvocationTargetException exception) {
			exception.printStackTrace();
		}
		if (events != null) {
			for (HashMap<String, String> event : events) {
				EventSystem.newEvent(event);
			}
		}
	}
	
	public static void act(List<Long> entities) {
		for (Long entity : entities) {
			act(entity);
			if (!Game.running) {
				return;
			}
		}
	}
	
	public static void RETest(String action, Long entity) {
		Point2D pos = ECSManager.getComponent(entity, "Position").coordinateValue();
		Point2D newPos = pos;
		switch (action) {
		case "MOVE_UP":
			newPos = new Point2D(pos.y - 1, pos.x);
			break;
		case "MOVE_DOWN":
			newPos = new Point2D(pos.y + 1, pos.x);
			break;
		case "MOVE_RIGHT":
			newPos = new Point2D(pos.y, pos.x + 1);
			break;
		case "MOVE_LEFT":
			newPos = new Point2D(pos.y, pos.x - 1);
			break;
		case "MOVE_UP_RIGHT":
			newPos = new Point2D(pos.y - 1, pos.x + 1);
			break;
		case "MOVE_UP_LEFT":
			newPos = new Point2D(pos.y - 1, pos.x - 1);
			break;
		case "MOVE_DOWN_RIGHT":
			newPos = new Point2D(pos.y + 1, pos.x + 1);
			break;
		case "MOVE_DOWN_LEFT":
			newPos = new Point2D(pos.y + 1, pos.x - 1);
			break;
		case "STRIKE":
			Screen.get().rhetoricalPrompt("Strike where?", Colors.libTCOD.white.normal);
			newPos = getUserSelectedSquare(1);
			EventSystem.newEvent(Game.currentStep, entity, "strike", newPos.toString());
			return;
		case "REST":
			int time = 100;
			if (ECSManager.hasComponent(entity, "MovementSpeed")) {
				time -= ECSManager.getComponent(entity, "MovementSpeed").doubleValue();
			}
			EventSystem.newEvent(Game.currentStep + time, entity, "turn", (String)null);
			return;
		default:
			break;
		}
		Long bEntity = ECSLevel.getFirstEntityWith(newPos, "Behaviour", null);
		if (bEntity != null) {
			EventSystem.newEvent(Game.currentStep, entity, "strike", newPos.toString());
			return;
		}
		EventSystem.newEvent(Game.currentStep, entity, "move", newPos.toString());
	}
	
	public static void calculateSecondaries(Long entity, String... secondaryList) {
		HashMap<String, Double> secondaries = new HashMap<>();
		for (String secondaryName : secondaryList) {
			Double value = null;
			String str, con, dex, agi, intl, wis, cre, per;
			str = ECSManager.getComponentValue(entity, Names.strength);
			con = ECSManager.getComponentValue(entity, Names.constitution);
			dex = ECSManager.getComponentValue(entity, Names.dexterity);
			agi = ECSManager.getComponentValue(entity, Names.agility);
			intl = ECSManager.getComponentValue(entity, Names.intelligence);
			wis = ECSManager.getComponentValue(entity, Names.wisdom);
			cre = ECSManager.getComponentValue(entity, Names.creativity);
			per = ECSManager.getComponentValue(entity, Names.perception);
			switch (secondaryName) {
			case "PhysicalAttack":
				if (str != null && con != null && dex != null) {
					value = ((Integer.valueOf(str) * 5) 
					+ (Integer.valueOf(con) * 2) 
					+ (Integer.valueOf(dex) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "PhysicalAccuracy":
				if (dex != null && cre != null && agi != null) {
					value = ((Integer.valueOf(dex) * 5) 
					+ (Integer.valueOf(cre) * 2) 
					+ (Integer.valueOf(agi) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "PhysicalDefense":
				if (str != null && con != null) {
					value = ((Integer.valueOf(con) * 5) 
					+ (Integer.valueOf(str) * 3))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "PhysicalEvasion":
				if (agi != null && dex != null && str != null) {
					value = ((Integer.valueOf(agi) * 5) 
					+ (Integer.valueOf(dex) * 2) 
					+ (Integer.valueOf(str) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "PhysicalSpeed":
				if (agi != null && dex != null) {
					value = ((Integer.valueOf(agi) * 3) 
					+ (Integer.valueOf(dex) * 2))
					/ 1D;
					value = ECSTools.applyCurve(value, 7D, 5D);
				}
				break;
			case "MentalAttack":
				if (intl != null && cre != null && wis != null) {
					value = ((Integer.valueOf(intl) * 5) 
					+ (Integer.valueOf(cre) * 2) 
					+ (Integer.valueOf(wis) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "MentalAccuracy":
				if (cre != null && per != null && wis != null) {
					value = ((Integer.valueOf(cre) * 5) 
					+ (Integer.valueOf(per) * 2) 
					+ (Integer.valueOf(wis) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "MentalDefense":
				if (wis != null && con != null && intl != null) {
					value = ((Integer.valueOf(wis) * 5) 
					+ (Integer.valueOf(con) * 2) 
					+ (Integer.valueOf(intl) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "MentalEvasion":
				if (per != null && wis != null && intl != null) {
					value = ((Integer.valueOf(per) * 5) 
					+ (Integer.valueOf(wis) * 2) 
					+ (Integer.valueOf(intl) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "MentalSpeed":
				if (per != null && intl != null) {
					value = ((Integer.valueOf(per) * 3) 
					+ (Integer.valueOf(intl) * 2))
					/ 1D;
					value = ECSTools.applyCurve(value, 7D, 5D);
				}
				break;
			case "MovementSpeed":
				if (agi != null && str != null) {
					value = ((Integer.valueOf(agi) * 1) 
					+ (Integer.valueOf(str) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 7D, 5D);
				}
				break;
			case "SpecialDefense":
				if (con != null && wis != null && cre != null) {
					value = ((Integer.valueOf(con) * 1) 
					+ (Integer.valueOf(wis) * 1) 
					+ (Integer.valueOf(cre) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			case "SpecialInflict":
				if (cre != null && intl != null) {
					value = ((Integer.valueOf(cre) * 2) 
					+ (Integer.valueOf(intl) * 1))
					/ 1D;
					value = ECSTools.applyCurve(value, 40D, 40D);
				}
				break;
			default:
				value = 0D;
				break;
			}
			secondaries.put(secondaryName, value);
		}
		for (String name : secondaries.keySet()) {
			ECSManager.setComponent(entity, name, secondaries.get(name).toString());
		}
	}
	
	public static Point2D getUserSelectedSquare(Integer max) {
		int startY, startX;
		int selectY = (Config.mapWindowSize.y / 2) + Config.mapWindowPosition.y;
		int selectX = (Config.mapWindowSize.x / 2) + Config.mapWindowPosition.x;
		startY = selectY;
		startX = selectX;
		int resetBg = Screen.get().getCellBg(selectY, selectX);
		Point2D selection = null;
		
		int ch = BlackenKeys.NO_KEY;
		while (ch != 'x' && ch != KeyCode.Return.codePoint 
				&& ch != KeyCode.NumEnter.codePoint) {
			Screen.get().setCellColors(selectY, selectX, null, resetBg);
			if (ch == KeyCode.Up.codePoint
					|| ch == KeyCode.Num8.codePoint
					|| ch == KeyCode.Num7.codePoint
					|| ch == KeyCode.Num9.codePoint) {
				selectY--;
			}
			if (ch == KeyCode.Down.codePoint
					|| ch == KeyCode.Num2.codePoint
					|| ch == KeyCode.Num1.codePoint
					|| ch == KeyCode.Num3.codePoint) {
				selectY++;
			}
			if (ch == KeyCode.Left.codePoint
					|| ch == KeyCode.Num4.codePoint
					|| ch == KeyCode.Num7.codePoint
					|| ch == KeyCode.Num1.codePoint) {
				selectX--;
			}
			if (ch == KeyCode.Right.codePoint
					|| ch == KeyCode.Num6.codePoint
					|| ch == KeyCode.Num9.codePoint
					|| ch == KeyCode.Num3.codePoint) {
				selectX++;
			}
			if (selectY < Config.mapWindowPosition.y) {
				selectY = Config.mapWindowPosition.y;
			}
			if (selectX < Config.mapWindowPosition.x) {
				selectX = Config.mapWindowPosition.x;
			}
			if (selectY >= Config.mapWindowPosition.y
					+ Config.mapWindowSize.y) {
				selectY = Config.mapWindowPosition.y
						+ Config.mapWindowSize.y - 1;
			}
			if (selectX >= Config.mapWindowPosition.x
					+ Config.mapWindowSize.x) {
				selectX = Config.mapWindowPosition.x
						+ Config.mapWindowSize.x - 1;
			}
			if (Math.abs(selectY - startY) > max
					&& max >= 0) {
				if (selectY < startY) {
					selectY = startY - max;
				}
				if (selectY > startY) {
					selectY = startY + max;
				}
			}
			if (Math.abs(selectX - startX) > max
					&& max >= 0) {
				if (selectX < startX) {
					selectX = startX - max;
				}
				if (selectX > startX) {
					selectX = startX + max;
				}
			}
			resetBg = Screen.get().getCellBg(selectY, selectX);
			Screen.get().setCellColors(selectY, selectX, null, Colors.libTCOD.grey.dark);
			ch = Screen.get().getch();
		}
		
		if (ch == KeyCode.Return.codePoint 
				|| ch == KeyCode.NumEnter.codePoint) {
			selectY = selectY - startY + ECSManager.getComponent(getLastActiveControllable(), "Position").coordinateValue().y;
			selectX = selectX - startX + ECSManager.getComponent(getLastActiveControllable(), "Position").coordinateValue().x;
			selection = new Point2D(selectY, selectX);
		}
		
		Screen.get().setCellColors(selectY, selectX, null, resetBg);
		return selection;
	}
	
	public enum ControlAction {
		QUIT {
			@Override
			public void process() {
				String confirm = Screen.get().prompt("Really quit? (Y/N) ");
				if (confirm.length() > 0 && confirm.charAt(0) == 'Y') {
					LevelProvider.save();
					Game.restart = true;
					Game.running = false;
				}
			}
		},
		LOOK {
			@Override
			public void process() {
				getUserSelectedSquare(-1);
			}
		},
		SPAWN_MENU {
			@Override
			public void process() {
				Menu spawnMenu = new Menu("Spawn Entity", Colors.libTCOD.orange.dark);
				List<String> assemblages = ECSManager.getAllAssemblages();
				for (String asmblg : assemblages) {
					String descStr = "";
					for (String cmp : ECSManager.getAssemblage(asmblg).keySet()) {
						switch (cmp) {
						case "ValidActions":
						case "PlayerNature":
						case "Position":
						case "DisplayCharacter":
						case "Color":
							break;
						default:
							descStr += cmp + ": " + ECSManager.getAssemblage(asmblg).get(cmp) + "\n";
						}
					}
					spawnMenu.addOption(asmblg, Colors.libTCOD.sepia.light, descStr, Colors.libTCOD.sepia.light);
				}
				Point2D pos = null;
				String choice = null;
				while (pos == null) {
					choice = spawnMenu.show();
					if (choice == null) {
						Game.updateAll();
						return;
					}
					
					Game.updateAll();
					Screen.get().rhetoricalPrompt("Spawn where?", Colors.libTCOD.white.normal);
					pos = getUserSelectedSquare(-1);
				}
				EventSystem.newEvent(Game.currentStep, -1L, "spawn", choice, pos.toString());
				Game.updateAll();
			}
		},
		KILL_ENTITY
		{
			@Override
			public void process() {
				Screen.get().rhetoricalPrompt("Kill which entity?", Colors.libTCOD.white.normal);
				Point2D pos = getUserSelectedSquare(-1);
				if (pos != null) {
					Long eID = ECSLevel.getFirstEntityWith(pos,"Behaviour", null);
					if (eID != null) {
						ECSManager.killEntity(eID);
						Game.updateAll();
					}
				}
			}
		},
		INVENTORY
		{
			@Override
			public void process() {
				if (!ECSManager.hasComponent(lastActiveControllableEntity, "Inventory")) {
					GameLogger.get().addMessage("You're not carrying anything.", Colors.libTCOD.white.normal);
					return;
				}
				Menu invMenu = new Menu("Inventory", Colors.libTCOD.amber.dark);
				List<String> items = Arrays.asList(ECSManager.getComponent(
						lastActiveControllableEntity, "Inventory").value().split(","));
				for (String item : items) {
					String descStr = "";
					for (ComponentPair cmp : ECSManager.getAllComponents(Long.valueOf(item))) {
						switch (cmp.name()) {
						case "ValidActions":
						case "PlayerNature":
						case "Position":
						case "DisplayCharacter":
						case "Color":
							break;
						default:
							descStr += cmp.name() + ": " + cmp.value() + "\n";
						}
					}
					invMenu.addOption(item, Colors.libTCOD.amber.light, descStr, Colors.libTCOD.amber.light);
				}
				invMenu.show();
				Game.updateAll();
			}
		},
		STATUS
		{

			@Override
			public void process() {
				List<ComponentPair> cmps = ECSManager.getAllComponents(lastActiveControllableEntity);
				Screen.get().clear();
				for (int i = 0; i < cmps.size(); i++) {
					if (!(cmps.get(i).name().equals("ValidActions")
							|| cmps.get(i).name().equals("PlayerNature")
							|| cmps.get(i).name().equals("Position")
							|| cmps.get(i).name().equals("DisplayCharacter")
							|| cmps.get(i).name().equals("Color")) 
							&& i < Config.windowSize.y) {
						Screen.get().puts(cmps.get(i).name(), i + 5, 5, Colors.libTCOD.white.normal, Colors.libTCOD.black.normal);
						Screen.get().puts(cmps.get(i).value(), i + 5, Config.windowSize.x / 2 + 5, Colors.libTCOD.white.normal, Colors.libTCOD.black.normal);
					}
				}
				Game.waitForKey();
				Game.updateAll();
			}
			
		}
		;
		
		public abstract void process();
		
		public static List<String> names() {
			List<String> names = new Vector<>();
			for (ControlAction ctlAction : values()) {
				names.add(ctlAction.name());
			}
			return names;
		}
	}

}
